多态类型反序列化
一个接口 / 父类有多个实现,JSON 里带一个类型标识字段,反序列化时要还原成对应子类,这就是多态反序列化。典型场景:消息体、规则引擎、事件分发。
注解方式
用 @JsonTypeInfo 声明类型标识字段,@JsonSubTypes 列出所有子类型:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = TextMessage.class, name = "text"),
@JsonSubTypes.Type(value = ImageMessage.class, name = "image")
})
public abstract class Message {
public abstract String content();
}
public class TextMessage extends Message {
private String text;
@Override public String content() { return text; }
// getter/setter 省略
}
public class ImageMessage extends Message {
private String url;
@Override public String content() { return url; }
// getter/setter 省略
}
序列化时自动带上 type 字段,反序列化时按 type 选子类:
String json = "{\"type\":\"text\",\"text\":\"hello\"}";
Message msg = objectMapper.readValue(json, Message.class);
// msg 实际是 TextMessage
编程方式:registerSubtype
子类型很多、或不想在父类上硬编码枚举时,用 registerSubtype 动态注册,配合 @JsonTypeInfo 即可(不需要 @JsonSubTypes):
objectMapper.registerSubtypes(
new NamedType(TextMessage.class, "text"),
new NamedType(ImageMessage.class, "image")
);
封装成方法:
public static void registerSubtype(ObjectMapper objectMapper, Class<?> clazz, String type) {
objectMapper.registerSubtypes(new NamedType(clazz, type));
}
线程安全
registerSubtypes 会修改 ObjectMapper 内部状态,而 ObjectMapper 通常是全局共享单例。只能在应用初始化阶段、并发访问开始前调用;运行期动态注册会导致并发不一致。这点 Jackson 源码与官方文档都明确警告过。
如果确实要运行期动态加子类型,应新建一个独立的 ObjectMapper 实例,不要碰共享的那个。